#include "monte_carlo.h"

/* Gera um número aleatório entre o intervalo de [0, RAND_MAX] */
unsigned long long rand_number()
{
    return (unsigned long long) rand();
}

/* Verifica se um ponto está dentro de 1/4 da circunferência */
int in_circle()
{
    unsigned long long x = rand_number();
    unsigned long long y = rand_number();
    unsigned long long max = (unsigned long long) RAND_MAX;

    return (x*x + y*y <= max*max);
}

/* Cálculo de pontos dentro de uma circunferência */
unsigned int points_in_circle()
{
    unsigned int i, j;

	j = 0;
    for(i=0; i<NUM_POINTS; i++){
        if(in_circle()) 
			j++;
	}
    return j;
}

/* Calcula pi */
void calc_pi_mc(mpf_t pi, mpz_t in, mpz_t total)
{
    mpf_t aux;
    mpf_init(aux);

    mpf_set_z(aux, total);
	
    /* Calcula o valor de Pi usando a fórmula pi = 4*m/n */
    mpf_set_z(pi, in); 
	mpf_mul_ui(pi, pi, 4);
	mpf_div(pi, pi, aux);

    mpf_clear(aux);
}

/* Algoritmo sequencial de Monte Carlo */
void monte_carlo(mpf_t pi, int *iter)
{
	int i;
    mpf_t pi_ant; 
    mpz_t in, total;
   	
    srand(time(NULL)); /* Semeaçao para a geração de números aleatórios */

    /* Inicializa as variáveis */
	mpf_init(pi_ant);
    mpz_inits(in, total, NULL);

    for(i = 0; i < MAX_ITER; i++)
    {
        /* Incrementa a quantidade total de pontos */
        mpz_add_ui(total, total, NUM_POINTS);

        /* Incrementa a quantidade de pontos dentro da circunferência */
        mpz_add_ui(in, in, points_in_circle());

        /* Armazena o pi anterior para convergência */
        mpf_set(pi_ant, pi);

        /* Calcula o valor de pi */
        calc_pi_mc(pi, in, total);

		gmp_printf("total:%Zd --- %.*Ff\n", total, 10,pi);

        /* Verifica se convergiu */
        if(converge(pi_ant, pi)) 
			break;
    }
	
	/* Número de iterações utilizadas para calcular pontos aleatórios */
    *iter = i; 

    mpz_clears(in, total, NULL);
    mpf_clear(pi_ant);
}

/* Inicializa a struct para os cálculos paralelos */
void start_struct_mc(m_carlo *aux)
{
    int i;

	aux->conv = FALSE;
	
	mpz_init(aux->total);
	mpz_init(aux->in);
	mpf_init(aux->pi);
}

/* Limpa a struct */
void clear_struct_mc(m_carlo *aux)
{
    int i;

	mpz_clear(aux->total);
	mpz_clear(aux->in);
	mpf_clear(aux->pi);

}

/* Função da thread para calcular números aleatórios e fazer a contagem de m */
void *thread_calc_points_mc(void *arg)
{
    m_carlo *aux = (m_carlo *) arg;

    /* Armazena a quantidade de pontos */
    mpz_add_ui(aux->in, aux->in, points_in_circle());
	
    return NULL;
}

/* Função da thread que calcula o Pi e verifica convergência */
void thread_calc_pi_mc(m_carlo *aux)
{

    /* Quantidade de pontos totais utilizados será MAX_ITER * NUM_POINTS */
    mpz_set_ui(aux->total, MAX_ITER * NUM_POINTS);
    
	/* Calcula o novo valor de Pi */
    calc_pi_mc(aux->pi, aux->in, aux->total);
}

/* Algoritmo paralelo de Monte Carlo */
void monte_carlo_paral(mpf_t pi, int *iter)
{
    pthread_t thread[NUM_THREADS]; 
    m_carlo mc;
    int i, j;

    srand(time(NULL)); /* Semeia o algoritmo para calcular números aleatórios */
    start_struct_mc(&mc);
	mc.i = 0;

    	
    /* Criação das NTHREADS threads que calcularão valores de m */
    for(j = 0; j < NUM_THREADS; j++)
	{
        pthread_create(&thread[j], NULL, thread_calc_points_mc, &mc);
	}
		

	/* Sincroniza as threads */
    for(j = 0; j < 10; j++){
        pthread_join(thread[0], NULL);
		pthread_join(thread[1], NULL);
	}

	/* Calcula pi */
 	thread_calc_pi_mc(&mc);
	mpf_mul_ui(mc.pi, mc.pi, 10);
			
	gmp_printf("\nPi : %.*Ff", 10, mc.pi);

	/* Armazena o numero de iteraçoes */	
	*iter = MAX_ITER; 

  clear_struct_mc(&mc);
}
